using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security;
using Pinwheel.LSerialize.Debug;

namespace Pinwheel.LSerialize
{
    public static partial class Native
    {
        public struct ObjectHandle
        {
            public readonly IntPtr Address;

            public readonly ulong Identifier;

            public ObjectHandle(IntPtr address)
            {
                Address = address;
                Identifier = 0uL;
            }

            public ObjectHandle(ulong identifier)
            {
                Address = IntPtr.Zero;
                Identifier = identifier;
            }
        }

        public abstract class Allocator
        {
            private readonly HashSet<IntPtr> _allocated = new HashSet<IntPtr>();

            protected unsafe void TrackAlloc(void* ptr)
            {
                lock (_allocated)
                {
                    _allocated.Add((IntPtr) ptr);
                }
            }

            protected unsafe void TrackFree(void* ptr)
            {
                lock (_allocated)
                {
                    if (!_allocated.Remove((IntPtr) ptr))
                    {
                        throw new Exception($"Tried to free {(IntPtr) ptr} which was not allocated");
                    }
                }
            }

            public void Dispose()
            {
                lock (_allocated)
                {
                    if (_allocated.Count <= 0)
                    {
                        return;
                    }

                    Log.Error($"Found {_allocated.Count} allocations which leaked memory");
                    foreach (IntPtr val in _allocated)
                    {
                        Log.Error($"Undisposed Memory: {val}");
                    }
                }
            }

            public abstract unsafe void Free(void* ptr);

            public abstract unsafe void* Alloc(int count);

            public abstract unsafe void* Alloc(int count, int alignment);

            protected abstract unsafe void Clear(void* dest, int count);

            public unsafe void* AllocAndClear(int count)
            {
                void* ptr = Alloc(count);
                Clear(ptr, count);
                return ptr;
            }

            public unsafe void* AllocAndClear(int count, int alignment)
            {
                void* ptr = Alloc(count, alignment);
                Clear(ptr, count);
                return ptr;
            }

            public unsafe T* AllocAndClear<T>() where T : unmanaged
            {
                void* memory = Alloc(sizeof(T));
                Clear(memory, sizeof(T));
                return (T*) memory;
            }

            public unsafe T* Alloc<T>() where T : unmanaged
            {
                return (T*) Alloc(sizeof(T));
            }

            public unsafe void* Expand(void* buffer, int currentSize, int newSize)
            {
                Assert.Check(newSize > currentSize);
                void* newBuffer = AllocAndClear(newSize);
                Utils.Copy(newBuffer, buffer, currentSize);
                Free(buffer);
                return newBuffer;
            }

            public unsafe T* ExpandArray<T>(T* buffer, int currentSize, int newSize) where T : unmanaged
            {
                Assert.Check(newSize > currentSize);
                T* newBuffer = (T*) AllocAndClear(sizeof(T) * newSize);
                Utils.Copy(newBuffer, buffer, sizeof(T) * currentSize);
                Free(buffer);
                return newBuffer;
            }
        }

        public abstract class Utility
        {
            public abstract ObjectHandle HandleAcquire(object obj);

            public abstract void HandleRelease(ObjectHandle handle);

            public abstract object GetObjectForHandle(ObjectHandle handle);

            public abstract unsafe void Copy(void* dest, void* src, int count);

            public abstract unsafe void Clear(void* dest, int count);

            public abstract unsafe void Move(void* dest, void* src, int count);

            public abstract unsafe int Compare(void* ptr1, void* ptr2, int count);

            public unsafe void CopyArrayWithStride(void* source, int sourceIndex, void* destination,
                int destinationIndex, int count, int stride)
            {
                Copy((byte*) destination + destinationIndex * stride, (byte*) source + sourceIndex * stride,
                    count * stride);
            }

            public unsafe void CopyArray<T>(T* source, int sourceIndex, T* destination, int destinationIndex, int count)
                where T : unmanaged
            {
                CopyArrayWithStride(source, sourceIndex, destination, destinationIndex, count, sizeof(T));
            }

            public static unsafe void ClearFast(void* dest, int count)
            {
                Utils.Clear(dest, count);
            }

            public static unsafe void CopyFast(void* dest, void* src, int count)
            {
                Utils.Copy(dest, src, count);
            }
        }

        public static Allocator Current;
        public static Utility Utils;

        static Native()
        {
            Current = CreateNativeAllocator();
            Utils = CreateNativeUtils();
        }

        public static Allocator CreateNativeAllocator()
        {
#if UNITY_2019_1_OR_NEWER
            return new UnityAllocator();
#else
            switch (Environment.OSVersion.Platform)
            {
                case PlatformID.Unix:
                case PlatformID.MacOSX:
                    return new LIBCAllocator();
                default:
                    return new MSVCRTAllocator();
            }
#endif
        }

        public static Utility CreateNativeUtils()
        {
#if UNITY_2019_1_OR_NEWER
            return new UnityUtility();
#else
            switch (Environment.OSVersion.Platform)
            {
                case PlatformID.Unix:
                case PlatformID.MacOSX:
                    return new LIBCUtility();
                default:
                    return new MSVCRTUtility();
            }
#endif
        }

        public static int RoundUpToAlignment(int size, int alignment)
        {
            return alignment switch
            {
                1 => size,
                2 => (size + 1 >> 1) * 2,
                4 => (size + 3 >> 2) * 4,
                8 => (size + 7 >> 3) * 8,
                16 => (size + 15 >> 4) * 16,
                32 => (size + 31 >> 5) * 32,
                64 => (size + 63 >> 6) * 64,
                _ => throw new InvalidOperationException($"Invalid Alignment: {alignment}"),
            };
        }

        public static int GetAlignmentForArrayElement(int elementSize)
        {
            Assert.Check(elementSize > 0);
            if (elementSize > 0 && elementSize % 8 == 0)
            {
                return 8;
            }

            return 4;
        }
    }
}